package com.hwtx.form.util;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.*;
import lombok.experimental.SuperBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

@Data
@Slf4j
public class QueryParserResult implements Serializable {

    public static final String NO_NEED_TABLE = "no_need_table";
    private SelectTable table;
    private Project project = new Project();
    private BinaryNode<Filter> filterRoot;
    private GroupBy groupBy;
    private OrderBy orderBy;
    private Limit limit;
    private String rawExpress;
    private List<Filter> varFilter = Lists.newArrayList();
    private FunctionDef routerFunction;
    private List<JoinDef> joins = Lists.newArrayList();
    // table,变量信息
    private Map<String, Set<VarInfo>> vars = Maps.newHashMap();

    private static Pair<String, Boolean> getOrderByValue(OrderItem orderItem, Map<String, Object> params) {
        String param = (String) getVarValue(params, orderItem.getValue());
        String[] names = param.split("\\s+");
        if (names.length == 0) {
            log.error("解析错误, orderItem={}, param={}", JacksonUtil.object2json(orderItem), param);
            throw Exceptions.DS_ERROR();
        }
        if (names.length == 1) {
            return new ImmutablePair<>(names[0], orderItem.isAsc());
        } else if (names.length == 2) {
            return new ImmutablePair<>(names[0], StringUtils.equalsIgnoreCase(names[1], "asc"));
        } else {
            // 一个变量包含多个排序字段暂不支持
            log.error("单个变量包含多字段, orderItem={}, param={}", JacksonUtil.object2json(orderItem), param);
            return new ImmutablePair<>(names[0], StringUtils.equalsIgnoreCase(names[1], "asc"));
        }
    }

    public static Object getVarValue(Map<String, Object> values, Item value) {
        if (value == null) {
            return null;
        } else if (!value.isVariable()) {
            return value.getValue();
        }
        String key = (String) value.getValue();
        if (key.contains(".")) {
            String[] keys = key.split("\\.");
            return getValue(keys, values);
        }
        Object tValue = values.get(key);
        if (tValue == null && values.size() == 1) {
            Object param = values.values().stream().findFirst().get();
            tValue = BeanUtil.getFieldValue(param, (String) value.getValue());
        }
        if (tValue == null) {
            log.error("Dsl变量不存在，key = {}", value.getValue());
            throw Exceptions.DS_ERROR();
        }
        return tValue;
    }

    private static Object getValue(String[] key, Map<String, Object> values) {
        String first = key[0];
        Object object = values.get(first);
        if (object == null) {
            log.error("无法获取查询参数,key = {}", first);
            throw new RuntimeException("无法获取查询参数");
        }
        for (int i = 1; i < key.length; i++) {
            object = BeanUtil.getFieldValue(object, key[i]);
        }
        return object;
    }

    @Override
    public String toString() {
        StringBuilder joinBuilder = new StringBuilder();
        joinBuilder.append("{").append("\n");
        for (JoinDef item : joins) {
            joinBuilder.append("\t\t").append(item.toString()).append("\n");
        }
        joinBuilder.append("\t}");
        return "QueryParserResult = {" + "\n" +
                "\t" + "table = " + table + "\n" +
                "\t" + "project = " + project + "\n" +
                "\t" + "filterRoot = " + filterRoot + "\n" +
                "\t" + "groupBy = " + groupBy + "\n" +
                "\t" + "orderBy = " + orderBy + "\n" +
                "\t" + "limit = " + limit + "\n" +
                "\t" + "join = " + joinBuilder + "\n" +
                "\t" + "varFilter = " + varFilter + "\n" +
                "\t" + "routerFunction = " + routerFunction + "\n" +
                "}";
    }

    public String[] getRouterValue(Map<String, Object> params) {
        if (routerFunction == null) {
            return null;
        }
        UdfHandler udfHandler = UdfHandler.getUdfHandler(routerFunction.getName());
        if (udfHandler instanceof UdfHandler.RouterUdf) {
            return ((UdfHandler.RouterUdf) udfHandler).getRouterValue(params, routerFunction.getParameters());
        }
        return null;
    }

    public void addFilterVar(Filter filter) {
        if (filter.hasVar()) {
            varFilter.add(filter);
        }
    }

    public Map<String, Filter> filter2Map() {
        Map<String, Filter> ret = Maps.newHashMap();
        if (filterRoot == null) return ret;
        //访问根节点
        if (!(filterRoot.getData() instanceof BinaryFilter)) {
            if (filterRoot.getData() instanceof ColumnFilter) {
                ret.put(((ColumnFilter) filterRoot.getData()).getColumn(), filterRoot.getData());
            }
        }
        preOrder(filterRoot.getLeft(), ret); // 递归遍历左子树
        preOrder(filterRoot.getRight(), ret); // 递归遍历右子树
        return ret;
    }

    private void preOrder(BinaryNode<Filter> root, Map<String, Filter> ret) {
        if (root == null) {
            return;
        }
        Deque<BinaryNode<Filter>> stack = new ArrayDeque<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            BinaryNode<Filter> node = stack.pop();
            if (!(node.getData() instanceof BinaryFilter)) {
                if (node.getData() instanceof ColumnFilter) {
                    ret.put(((ColumnFilter) node.getData()).getColumn(), node.getData());
                }
            }
            if (node.getRight() != null) {
                stack.push(node.getRight()); // 右子树入栈
            }
            if (node.getLeft() != null) {
                stack.push(node.getLeft()); // 左子树入栈
            }
        }
    }

    public boolean hasVar() {
        boolean ret = !varFilter.isEmpty();
        if (groupBy != null) {
            ret = ret || groupBy.hasVar();
        }
        if (orderBy != null) {
            ret = ret || orderBy.hasVar();
        }
        if (limit != null) {
            ret = ret || limit.isOffsetVar() || limit.isRowCountVar();
        }
        return ret;
    }

    enum ValueType {
        STR, DOUBLE, LONG, NULL, FUNCTION
    }

    public enum SelectType {
        STR, AGG, FUNCTION, FUNCTIONS, NUM_DOUBLE, NUM_LONG, ALL;

        public static SelectType getSelectType(String name) {
            for (AggFunction aggFunction : AggFunction.values()) {
                if (aggFunction.name().equalsIgnoreCase(name)) {
                    return SelectType.AGG;
                }
            }
            return SelectType.FUNCTION;
        }
    }

    public enum AggFunction {
        SUM, COUNT, AVG, MAX, MIN;

        public static AggFunction getAgg(String name) {
            for (AggFunction aggFunction : AggFunction.values()) {
                if (aggFunction.name().equalsIgnoreCase(name)) {
                    return aggFunction;
                }
            }
            throw Exceptions.DS_CONFIG_ERROR("无法识别聚合函数类型,function = " + name);
        }

        public static boolean isAggFunction(String name) {
            for (AggFunction aggFunction : AggFunction.values()) {
                if (aggFunction.name().equalsIgnoreCase(name)) {
                    return true;
                }
            }
            return false;
        }
    }

    @Getter
    public enum Operator {
        ADD("+"), MULTIPLY("*"), DIV("/"), SUB("-");

        final String op;

        Operator(String op) {
            this.op = op;
        }

        public static Operator getOp(String op) {
            for (Operator operator : values()) {
                if (operator.op.equalsIgnoreCase(op)) {
                    return operator;
                }
            }
            throw Exceptions.DS_CONFIG_ERROR("无法识别运算符,op = " + op);
        }
    }

    @Getter
    @Builder
    public static class VarInfo {
        String column;
        String varName;
        Class<?> type;
    }

    @Data
    public static class JoinDef {
        SelectTable table;
        boolean left;
        boolean right;
        boolean inner;
        private BinaryNode<Filter> onFilterRoot;

        @Override
        public String toString() {
            return "table = " + table + "\n\t\t" +
                    "left = " + left + "\n\t\t" +
                    "right = " + right + "\n\t\t" +
                    "inner = " + inner + "\n\t\t" +
                    "onFilterRoot = " + onFilterRoot;
        }
    }

    @Data
    public static class Project implements Serializable {
        private List<SelectItem> columns = Lists.newArrayList();
        private Boolean distinct;

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("{").append("\n");
            for (SelectItem item : columns) {
                builder.append("\t\t").append(item.toString()).append("\n");
            }
            builder.append("\t}");
            return builder.toString();
        }
    }

    @Data
    @ToString
    public static class Filter implements Serializable {
        String express;

        public List<String> getVars() {
            return List.of();
        }

        boolean hasVar() {
            return false;
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    public static class BinaryFilter extends Filter {
        String op;
        BinaryFilter parent;

        @Override
        public String toString() {
            return "BinaryFilter {" +
                    "op = '" + op + '\'' +
                    '}';
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @ToString
    public static class ColumnFilter extends Filter {
        String column;
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @ToString
    public static class FunctionFilter extends Filter {
        FunctionDef function;

        @Override
        public List<String> getVars() {
            return function.getParameters().stream().filter(Item::isVariable).map(item -> item.getValue().toString()).collect(Collectors.toList());
        }

        boolean hasVar() {
            return function.getParameters().stream().anyMatch(Item::isVariable);
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    public static class NotEqualFilter extends ColumnFilter {
        Item value;

        @Override
        public List<String> getVars() {
            if (hasVar()) {
                return List.of(value.getValue().toString());
            }
            return null;
        }

        @Override
        boolean hasVar() {
            return value.isVariable();
        }

        public Object getValue(Map<String, Object> params) {
            return getVarValue(params, value);
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    public static class EqualFilter extends ColumnFilter {
        Item value;

        @Override
        public List<String> getVars() {
            if (hasVar()) {
                return List.of(value.getValue().toString());
            }
            return null;
        }

        @Override
        public String toString() {
            return "EqualFilter{" +
                    "value=" + value +
                    ", column='" + column + '\'' +
                    ", express='" + express + '\'' +
                    '}';
        }

        @Override
        boolean hasVar() {
            return value.isVariable();
        }

        public Object getValue(Map<String, Object> params) {
            return getVarValue(params, value);
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @ToString
    public static class IsNullFilter extends ColumnFilter {
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @ToString
    public static class IsNotNullFilter extends ColumnFilter {
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @ToString
    public static class InFilter extends ColumnFilter {
        private List<Item> values = Lists.newArrayList();

        @Override
        public List<String> getVars() {
            return values.stream().filter(Item::isVariable).map(item -> item.getValue().toString()).collect(Collectors.toList());
        }

        @Override
        boolean hasVar() {
            return values.stream().anyMatch(Item::isVariable);
        }

        public List<Object> getValue(Map<String, Object> params) {
            List<Object> ret = Lists.newArrayList();
            for (Item item : values) {
                if (item.isVariable()) {
                    Object t = getVarValue(params, item);
                    if (t instanceof Collection) {
                        ret.addAll((Collection<?>) t);
                    } else {
                        ret.add(t);
                    }
                } else {
                    ret.add(item.getValue());
                }
            }
            return ret;
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @ToString
    public static class GreaterAndMinorFilter extends ColumnFilter {
        Item value;

        @Override
        public List<String> getVars() {
            if (hasVar()) {
                return List.of(value.getValue().toString());
            }
            return null;
        }

        @Override
        boolean hasVar() {
            return value.isVariable();
        }

        public Object getValue(Map<String, Object> params) {
            return getVarValue(params, value);
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @ToString
    //todo like没处理
    public static class LikeFilter extends ColumnFilter {
        Item value;

        @Override
        public List<String> getVars() {
            if (hasVar()) {
                return List.of(value.getValue().toString());
            }
            return null;
        }

        @Override
        boolean hasVar() {
            return value.isVariable();
        }

        public String getValue(Map<String, Object> params) {
            return (String) getVarValue(params, value);
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @ToString
    public static class BetweenFilter extends ColumnFilter {
        Item start;
        Item end;

        @Override
        boolean hasVar() {
            return start.isVariable() || end.isVariable();
        }

        @Override
        public List<String> getVars() {
            List<String> ret = Lists.newArrayList();
            if (start.isVariable()) {
                ret.add(start.getValue().toString());
            }
            if (end.isVariable()) {
                ret.add(end.getValue().toString());
            }
            return ret;
        }

        public Object getStartValue(Map<String, Object> params) {
            return getVarValue(params, start);
        }

        public Object getEndValue(Map<String, Object> params) {
            return getVarValue(params, end);
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @ToString
    public static class GreaterThanFilter extends GreaterAndMinorFilter {
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @ToString
    public static class GreaterThanEqualsFilter extends GreaterAndMinorFilter {
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @ToString
    public static class LessThanFilter extends GreaterAndMinorFilter {
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @ToString
    public static class LessThanEqualsFilter extends GreaterAndMinorFilter {
    }

    @EqualsAndHashCode
    @Data
    @ToString
    public static class GroupBy implements Serializable {
        private List<Item> items = Lists.newArrayList();

        public boolean hasVar() {
            return items.stream().anyMatch(Item::isVariable);
        }

        public List<String> getValue(Map<String, Object> params) {
            List<String> ret = Lists.newArrayList();
            for (Item item : items) {
                if (item.isVariable()) {
                    Object t = getVarValue(params, item);
                    if (t instanceof Collection) {
                        ret.addAll((Collection<String>) t);
                    } else {
                        ret.add((String) t);
                    }
                } else {
                    ret.add((String) item.getValue());
                }
            }
            return ret;
        }
    }

    @EqualsAndHashCode
    @ToString
    @Data
    public static class Limit implements Serializable {
        private Item rowCount;
        private Item offset;

        boolean isOffsetVar() {
            return offset.isVariable();
        }

        boolean isRowCountVar() {
            return rowCount.isVariable();
        }

        public Integer getRowCount(Map<String, Object> params) {
            Object value = getVarValue(params, rowCount);

            if (Objects.nonNull(value)) {
                return (value instanceof Integer) ? (Integer) value : ((Long) value).intValue();
            }
            return 1;
        }

        public long getOffset(Map<String, Object> params) {
            Object value = getVarValue(params, offset);
            return Objects.nonNull(value) ? (Long) value : 0L;
        }
    }

    @EqualsAndHashCode
    @Data
    @ToString
    public static class OrderBy implements Serializable {
        private List<OrderItem> items = Lists.newArrayList();

        boolean hasVar() {
            return items.stream().anyMatch(OrderItem::isVariable);
        }
    }

    @Data
    public static class OrderItem implements Serializable {
        Item value;
        boolean asc;

        public boolean isVariable() {
            return value.isVariable();
        }

        public Pair<String, Boolean> getItem(Map<String, Object> params) {
            if (!isVariable()) {
                return new ImmutablePair<>((String) value.getValue(), asc);
            }
            return getOrderByValue(this, params);
        }
    }

    @Builder
    @Getter
    public static class Item implements Serializable {
        @Builder.Default
        private final boolean variable = false;
        private ValueType valueType;
        private Object value;

        @Override
        public String toString() {
            return "Item{" +
                    "valueType = " + valueType +
                    ", value = " + value +
                    ", variable = " + variable +
                    '}';
        }
    }

    @Getter
    @SuperBuilder
    public static class SelectTable implements Serializable {
        String name;
        String alias;
        String ds;

        @Override
        public String toString() {
            return "{" +
                    "name='" + name + '\'' +
                    ", alias='" + alias + '\'' +
                    '}';
        }
    }

    @Getter
    @SuperBuilder
    @EqualsAndHashCode
    public static class FunctionDef implements Serializable {
        private List<Item> parameters;
        private String name;
        private boolean distinct;
        private SelectType selectType;
        //是不是原子函数
        @Setter
        private boolean factor;

        @Override
        public String toString() {
            return " {" +
                    "parameters = " + parameters +
                    ", name = '" + name + '\'' +
                    ", distinct = " + distinct +
                    ", selectType = " + selectType +
                    ", factor = " + factor +
                    '}';
        }
    }

    @SuperBuilder
    public static class ExpressItem implements Serializable {

    }

    @SuperBuilder
    public static class LeftBracket extends ExpressItem {

    }

    @SuperBuilder
    public static class RightBracket extends ExpressItem {

    }

    @SuperBuilder
    @Getter
    public static class SelectItem extends ExpressItem {
        @Builder.Default
        final
        SelectType selectType = SelectType.STR;
        @Setter
        String alias;
        String item;
        String table;

        @Override
        public String toString() {
            return "SelectItem = {" +
                    "alias='" + alias + '\'' +
                    ", item='" + item + '\'' +
                    ", selectType=" + selectType +
                    '}';
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @SuperBuilder
    @Getter
    @ToString
    public static class BinaryGroup extends SelectItem {
        ExpressItem left;
        ExpressItem right;
        BinarySelectItem op;
    }

    @EqualsAndHashCode(callSuper = true)
    @SuperBuilder
    @Getter
    @ToString
    public static class BinarySelectItem extends SelectItem {
        Operator operator;
        @Setter
        BinarySelectItem parent;
    }

    @EqualsAndHashCode(callSuper = true)
    @SuperBuilder
    @Getter
    static class StrSelectItem extends SelectItem {
    }

    @EqualsAndHashCode(callSuper = true)
    @SuperBuilder
    @Getter
    public static class AllSelectItem extends SelectItem {

    }

    @EqualsAndHashCode(callSuper = true)
    @SuperBuilder
    @Getter
    @ToString
    public static class DoubleSelectItem extends SelectItem {
        Double value;
    }

    @EqualsAndHashCode(callSuper = true)
    @SuperBuilder
    @Getter
    @ToString
    public static class LongSelectItem extends SelectItem {
        Long value;
    }

    @EqualsAndHashCode(callSuper = true)
    @Getter
    @SuperBuilder
    public static class FunctionSelectItem extends SelectItem {
        FunctionDef function;

        @Override
        public String toString() {
            return "FunctionSelectItem = { " +
                    "function=" + function +
                    ", alias = '" + alias + '\'' +
                    ", item = '" + item + '\'' +
                    ", selectType = " + selectType +
                    '}';
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @SuperBuilder
    @Getter
    @ToString
    public static class FunctionsSelectItem extends SelectItem {
        BinaryNode<SelectItem> functionsRoot;
    }
}
